package tree;

import java.util.ArrayList;
import java.util.Random;

public abstract class Node{
	protected ArrayList<Node> sons;	
	protected int sonsNeeded;
	protected Node parent;
	protected Random random;
	protected int depth;
	protected boolean hasConstSon;
	
	//---abstract methods--//
	public abstract void initSons(int depth, int maximumDepth,TreeNodesManager treeNodesManager);
	public abstract Node getRandNodeWithTheSameReturnType(int maxDepth, TreeNodesManager treeNodesManager);
	protected abstract ArrayList<Node> getProperNodesManagerList(TreeNodesManager treeNodeManager);
	public abstract Node getRandNode(int maxDepth, TreeNodesManager treeNodesManager);
	protected abstract Node clone(TreeNodesManager treeNodesManager,Node parent);
	protected abstract Node clone(Node parent);
	//------------------//
	public Node() {}
	public boolean isLeaf(){
		return sons.isEmpty();
	}
	public Node(int sonsNeeded,Node parent,int depth) {
		this.sons=new ArrayList<Node>();
		this.sonsNeeded=sonsNeeded;
		this.parent=parent;
		this.random=new Random();
		this.depth=depth;
		this.hasConstSon=false;
		//System.out.println("new node: "+toString());
	}
	
	public ArrayList<Node> getSons() {
		return sons;
	}
	
	public int getDepth() {
		return depth;
	}

	public void addSon(Node son){
		sons.add(son);
	}
	
	public int getSonsNeeded() {
		return sonsNeeded;
	}
	
	
	public Node getIthSon(int index){
		return sons.get(index);
	}


	
	public Node getParent() {
		return parent;
	}
	
	/*
	 * replace a son in the node's sons arrayList
	 * 
	 * @param oldNode the node we want to replace
	 * @param newNode the node we want to replace WITH 
	 */
	private void replaceSon(Node oldNode, Node newNode,TreeNodesManager treeNodeManager) {
		sons.set(sons.indexOf(oldNode), newNode);
		ArrayList<Node>list = oldNode.getProperNodesManagerList(treeNodeManager);
		if (list!=null){
			list.remove(oldNode);
			list.add(newNode);
		}
		treeNodeManager.getAllNodesInTree().remove(oldNode);
		treeNodeManager.getAllNodesInTree().add(newNode);
	}
	//replace old node sub tree including it with new node and its sub tree
	private void replaceSonSubTree(Node oldNode, Node newNode,TreeNodesManager treeNodeManager) {

		sons.set(sons.indexOf(oldNode), newNode);		
		oldNode.deleteSubTreeFromManager(treeNodeManager);
		newNode.addSubTreeToManager(treeNodeManager);
		
	}
	
	//add node and it's subtree to tree nodes manager
	private void addSubTreeToManager(TreeNodesManager treeNodeManager) {
		
		ArrayList<Node>list = getProperNodesManagerList(treeNodeManager);
		//only NOT operator can return null cause he isn't in any list beside all nodes list
		if (list!=null){
			list.add(this);
		}		
		treeNodeManager.getAllNodesInTree().add(this);
		
		for (Node son : sons){
			son.addSubTreeToManager(treeNodeManager);
		}
	}
	
	//remove add node and it's subtree from tree nodes manager
	protected void deleteSubTreeFromManager(TreeNodesManager treeNodeManager){
		
		ArrayList<Node>list = getProperNodesManagerList(treeNodeManager);
		//only NOT operator can return null cause he isn't in any list beside all nodes list
		if (list!=null){
			list.remove(this);
		}		
		treeNodeManager.getAllNodesInTree().remove(this);
		
		for (Node son : sons){
			son.deleteSubTreeFromManager(treeNodeManager);
		}
	}
	
	

	
	@Override
	public boolean equals(Object other) {
		if (other == null || !(other instanceof Node)) {
			return false;
		}
		Node otherNode = (Node) other;
		return this.sons == otherNode.sons;
	}

	
	public String printSubTree(int depth){
		StringBuilder sb = new StringBuilder();
		for (int i=0;i<depth;i++){
			sb.append("\t");
		}
		sb.append(this.toString()+":\n");
		
		for (Node son : sons){			
			sb.append(son.printSubTree(depth+1)).append("\n");			
		}
		return sb.toString();
	}
	/**
	 * replace current node with given one
	 * @param replaceWith given node
	 * @param treeNodeManager tree nodes manager
	 */
	public void replace(Node replaceWith,TreeNodesManager treeNodeManager) {
		getParent().replaceSon(this,replaceWith,treeNodeManager);
		//disconnect old node from his parent
		this.parent=null;
		
	}
	/**
	 * replace sub tree of node including node itself with given node
	 * @param replaceWith given node
	 * @param treeNodeManager manager
	 */
	public void replaceSubTree(Node replaceWith,TreeNodesManager treeNodeManager){
		getParent().replaceSonSubTree(this,replaceWith,treeNodeManager);
		//attach new node to correct parent
		replaceWith.parent=this.parent;
		//disconnect old node from his parent
		this.parent=null;
		replaceWith.setSubNodesDepth(depth);
		
	}
	
	public boolean isHasConstSon() {
		return hasConstSon;
	}
	
	public void setHasConstSon(boolean hasConstSon) {
		this.hasConstSon = hasConstSon;
	}
	
	//---------Functions needed for XML file----------------//
	public Random getRandom() {
		return random;
	}
	public void setParent(Node parent) {
		this.parent = parent;
	}
	public void setRandom(Random random) {
		this.random = random;
	}
	public void setSons(ArrayList<Node> sons) {
		this.sons = sons;
	}
	public void setSonsNeeded(int sonsNeeded) {
		this.sonsNeeded = sonsNeeded;
	}
	public void setDepth(int depth) {
		this.depth = depth;
	}
	//---------------------------------------------------//
	//recursively set sub tree nodes depth
	public void setSubNodesDepth(int newDepthOfNode) {
		setDepth(newDepthOfNode);
		for (Node son : sons){
			son.setSubNodesDepth(newDepthOfNode+1);
		}		
	}
	
	private int recursiveDepthSearch(Node currentNode){
		int leftSon = 0;
		int rightSon = 0;
		if(currentNode.sons.isEmpty()){
		        return 0;
		}else{
			if (currentNode.sons.size()>=1){
				leftSon = recursiveDepthSearch(currentNode.getSons().get(0));
			}
			if (currentNode.sons.size()>=2){
				rightSon = recursiveDepthSearch(currentNode.getSons().get(1));
			}
		    if( rightSon < leftSon  || leftSon == rightSon){
		        return (leftSon + 1);
		    }
		    else{
		        return (rightSon + 1);
		    }
		}
	}
	
	public int getSubNodeDepth() {
		return recursiveDepthSearch(this);
	}
	
	
	public Node cloneSubTree(TreeNodesManager treeNodesManager,Node parent) {		
		return clone(treeNodesManager,parent);
	}
	public Node cloneSubTree(Node parent) {		
		return clone(parent);
	}
	
	
}
